/**
 * @file nor_sfud.c
 * @author LokLiang (lokliang@163.com)
 * @brief 把 SFUD 模块关联到 storage_nor.h 的代码
 * @version 0.1
 * @date 2023-06-07
 *
 * @copyright Copyright (c) 2023
 *
 */

#include "components/storage_nor.h"
#include "drivers/ext/spi_dev.h"

#include "sfud/inc/sfud.h"

#include "sys_log.h"

static int _nor_init(storage_nor_t nor_hdl, const char *device_name, void *arg);
static int _nor_deinit(storage_nor_t nor_hdl);
static void _nor_lock(storage_nor_t nor_hdl);
static void _nor_unlock(storage_nor_t nor_hdl);
static uint16_t _nor_get_sector_size(storage_nor_t nor_hdl);
static uint32_t _nor_get_chip_size(storage_nor_t nor_hdl);
static int _nor_erase(storage_nor_t nor_hdl, uint32_t addr, uint32_t size);
static int _nor_write(storage_nor_t nor_hdl, uint32_t addr, const void *src, uint32_t size);
static int _nor_read(storage_nor_t nor_hdl, void *dest, uint32_t addr, uint32_t size);
static int _nor_read_async(storage_nor_t nor_hdl, void *dest, uint32_t addr, uint32_t size, storage_nor_read_cb cb, void *arg);
static bool _nor_is_busy(storage_nor_t nor_hdl);
static int _nor_set_power(storage_nor_t nor_hdl, bool power_on);

static storage_nor_cfg_t s_config;

static struct storage_nor const s_storage = {
    .api = {
        .init = _nor_init,
        .deinit = _nor_deinit,
        .lock = _nor_lock,
        .unlock = _nor_unlock,
        .get_sector_size = _nor_get_sector_size,
        .get_chip_size = _nor_get_chip_size,
        .erase = _nor_erase,
        .write = _nor_write,
        .read = _nor_read,
        .read_async = _nor_read_async,
        .is_busy = _nor_is_busy,
        .set_power = _nor_set_power,
    },
    .cfg = &s_config,
};
OBJ_EXPORT_COMPONENT(s_storage, "NOR:SFUD");

static int _nor_init(storage_nor_t nor_hdl, const char *device_name, void *arg)
{
    _nor_deinit(nor_hdl);

    sfud_flash *flash = malloc(sizeof(sfud_flash));
    if (flash == NULL)
    {
        SYS_LOG_WRN("Insufficient memory");
        return -1;
    }

    memset(flash, 0, sizeof(*flash));
    flash->name = (char *)component_name(nor_hdl);
    flash->spi.name = (char *)device_name;
    flash->spi.user_data = NULL;
    if (sfud_device_init(flash) == SFUD_SUCCESS)
    {
        nor_hdl->cfg->data = flash;
        return 0;
    }
    else
    {
        _nor_deinit(nor_hdl);
        return -1;
    }
}

static int _nor_deinit(storage_nor_t nor_hdl)
{
    sfud_flash *flash = nor_hdl->cfg->data;

    if (flash)
    {
        if (_nor_is_busy(nor_hdl))
        {
            return 1;
        }

        spi_dev_deinit(flash->spi.user_data);
        free(flash);

        nor_hdl->cfg->data = NULL;
    }

    return 0;
}

static void _nor_lock(storage_nor_t nor_hdl)
{
    sfud_flash *flash = nor_hdl->cfg->data;
    spi_dev_lock(flash->spi.user_data);
}

static void _nor_unlock(storage_nor_t nor_hdl)
{
    sfud_flash *flash = nor_hdl->cfg->data;
    spi_dev_unlock(flash->spi.user_data);
}

static uint16_t _nor_get_sector_size(storage_nor_t nor_hdl)
{
    sfud_flash *flash = nor_hdl->cfg->data;
    return flash->chip.erase_gran;
}

static uint32_t _nor_get_chip_size(storage_nor_t nor_hdl)
{
    sfud_flash *flash = nor_hdl->cfg->data;
    return flash->chip.capacity;
}

static int _nor_erase(storage_nor_t nor_hdl, uint32_t addr, uint32_t size)
{
    if (_nor_is_busy(nor_hdl))
    {
        return 1;
    }

    sfud_flash *flash = nor_hdl->cfg->data;
    if (sfud_erase(flash, addr, size) == SFUD_SUCCESS)
    {
        return 0;
    }
    else
    {
        return -1;
    }
}

static int _nor_write(storage_nor_t nor_hdl, uint32_t addr, const void *src, uint32_t size)
{
    if (_nor_is_busy(nor_hdl))
    {
        return 1;
    }

    sfud_flash *flash = nor_hdl->cfg->data;
    if (sfud_write(flash, addr, size, src) == SFUD_SUCCESS)
    {
        return 0;
    }
    else
    {
        return -1;
    }
}

static int _nor_read(storage_nor_t nor_hdl, void *dest, uint32_t addr, uint32_t size)
{
    if (_nor_is_busy(nor_hdl))
    {
        return 1;
    }

    sfud_flash *flash = nor_hdl->cfg->data;
    if (sfud_read(flash, addr, size, dest) == SFUD_SUCCESS)
    {
        return 0;
    }
    else
    {
        return -1;
    }
}

static int _nor_read_async(storage_nor_t nor_hdl, void *dest, uint32_t addr, uint32_t size, storage_nor_read_cb cb, void *arg)
{
    int ret = _nor_read(nor_hdl, dest, addr, size);
    if (ret == 0)
    {
        if (cb)
        {
            cb(nor_hdl, arg);
        }
    }
    return ret;
}

static bool _nor_is_busy(storage_nor_t nor_hdl)
{
    return false;
}

static int _nor_set_power(storage_nor_t nor_hdl, bool power_on)
{
    return 0;
}
